Animations can be very powerful tools when used effectively, allowing you to see progression over time, iterate through plots, and help you tell a better story with the data. This guide will give an overview of packages, techniques, and tips for creating animated plots in R.

In this guide you will learn:

  • Necessary packages for creating animated plots
  • How to turn static plots into animations
  • Components of gganimate and what each function does
  • Ways to edit, improve, and save your animations

Getting Started

Packages

To create animations, there are some R packages you will need to install & load first. The main packages we will be using are:

  1. gganimate
  2. ggplot2
  3. tidyverse
  4. gapminder

You will likely need to install the following packages if you haven’t worked with animations before:

install.packages("gganimate")
install.packages("gapminder")
install.packages("transformr")

Then load the necessary packages:

library(gganimate)
library(ggplot2)
library(tidyverse)
library(gapminder)
library(lubridate)

Data

To create animated plots we must first have some tidy data to work with. We’ll start with an unique data set to get familiar with some of the basic features and then later explore some example data.

To Do:

  1. Head over to Google Search Trends and search up a term that interests you.You should get a graph of the popularity of the search term over time.
  2. Click “add comparison” at the top and add at least two more terms (so you have 3 or more total).
  3. Select a 12 month data period.

One you have a graph of multiple terms, download this as a .csv file to your computer.

Note: Once you have your .csv downloaded, click on it in the bottom right “Files” pane and click “View File”. The .csv file will open in another tab. Delete the first two rows so that “Week” and the terms you searched are now at the top. Change the column names to something short and simple. Click “Save”, and now your .csv is ready for importing. Save it to the same folder as this file.

popularity <- read_csv("multiTimeline.csv")
popularity$Week <- mdy(popularity$Week)
head(popularity)
popularity <- read_csv("/Users/nolan/MACALESTER/Stat 456/multiTimeline.csv")
head(popularity)
## # A tibble: 6 × 4
##   Week       `World cup` `Premier League` `UEFA Champions League`
##   <date>           <dbl>            <dbl>                   <dbl>
## 1 2017-09-24           1                5                       7
## 2 2017-10-01           3                3                       1
## 3 2017-10-08           4                3                       1
## 4 2017-10-15           1                4                       7
## 5 2017-10-22           2                5                       1
## 6 2017-10-29           1                4                       8

We want this data in tidy format, so lets pivot_longer our data.

popularity <- popularity %>% 
  pivot_longer(cols = `*first column*`:`*last column*`,
               names_to = "Search Term",
               values_to = "Popularity")
popularity <- popularity %>% 
  pivot_longer(cols = `World cup`:`UEFA Champions League`,
               names_to = "Search Term",
               values_to = "Popularity")

Now make a line plot the popularity over time:

popularity %>% 
  ggplot(aes(x = Week, y = Popularity, group = `Search Term`, color = `Search Term`)) +
  geom_line() +
  labs(x = "Time", y = "Popularity") +
  theme_classic() +
  theme(legend.position = "top")

Now we’re ready for animations.

Creating Animations

Within gganimate, the main concept is the use of transition functions to create an animation. Other components of an animation the we will cover along with these functions are:

  • Labels
  • Views
  • Rendering
  • Shadows
  • Easing

We will walk through each of these in detail and build upon them to create some cool visualizations.

Basics

Transitions control how the data gets displayed in your animation. Do you want your data to populate over time by year, by category, or another filter? This is determined by the transition function you select. The list of common transition functions is shown below, along with their functionality.

Name Functionality
transition_reveal Reveal data along a given dimension
transition_filter Transition between different filters
transition_states Transition between several distinct stages of the data
transition_time Transition through distinct states in time
transition_layers Build up a plot, layer by layer

Transition functions are simply added to your regular ggplot statements following a “+”.

Transition functions also have a few common arguments that are useful to know as you often will want to adjust these. Typically, whatever variable you are animating on goes first. In addition, the other main arguments are:

Name Functionality
transition_length Controls the relative length of individual transitions
filter/layer/state_length Controls the relative length of pause at each state
range Controls the time range to animate (if applicable)
wrap Controls if the animation should wrap-around back to the start at the end

There are of course other arguments specific to each transition function, however these are the crucial ones to know when building animations.

Transition functions & Animation Components

Transition_reveal()

Lets try using transition_reveal() first. This transition will reveal the data along a given dimension – in this case time. This can be a good option when you have line charts or time-series data and want to show a progression of data over that dimension.

To Do:

Add transition_reveal() to your plot, with the Week variable as the first argument.

popularity %>% 
  ggplot(aes(x = Week, y = Popularity, group = `Search Term`, color = `Search Term`)) +
  geom_line() +
  labs(x = "Time", y = "Popularity") +
  theme_classic() +
  theme(legend.position = "top") +
  ???
popularity %>% 
  ggplot(aes(x = Week, y = Popularity, group = `Search Term`, color = `Search Term`)) +
  geom_line() +
  labs(x = "Time", y = "Popularity") +
  theme_classic() +
  theme(legend.position = "top") +
  transition_reveal(Week)

You should now see your plot animated in the viewer in the bottom right panel of your screen!

Labels

Another thing we should make sure to add to our animation is how it is transitioning. While we know the animation is changing by week, our viewer may not realize or understand that. With some transition functions this may or may not be easily inferable, so it’s good practice to include this information on the plot.

We can add this information in the labs() part of our ggplot statement, specifying one of the corresponding transition variables related to the transition function we are using (see below).

Name Label variable(s)
transition_reveal frame_along
transition_filter previous_filter, closest_filter, next_filter
transition_states previous_state, closest_state, next_state
transition_time frame_time
transition_layers previous_layer, closest_layer, next_layer, nlayers

To Do:

In this case, lets add title = “Week: {frame_along}” to our label.

popularity %>% 
  ggplot(aes(x = Week, y = Popularity, group = `Search Term`, color = `Search Term`)) +
  geom_line() +
  labs(x = "Time", y = "Popularity", ???) +
  theme_classic() +
  theme(legend.position = "top") +
  transition_reveal(Week)
popularity %>% 
  ggplot(aes(x = Week, y = Popularity, group = `Search Term`, color = `Search Term`)) +
  geom_line() +
  labs(x = "Time", y = "Popularity", title = "Week: {frame_along}") +
  theme_classic() +
  theme(legend.position = "top") +
  transition_reveal(Week)

Now we can see each week at the top as it is being displayed.

Views

Another thing we can alter about the animation is how the scales change. Currently they are static, which may not be the best when we have spread out data like this. You can change how the animation is viewed via view functions. The main one is:

  • view_follow()

You can also fix the x or y axis separately if it makes sense (it does in this case as we have values on a fixed scale from 0-100), using the fixed_x and fixed_y arguments.

To Do:

Try adding view_follow(fixed_y = TRUE) to the previous animation as see how it looks.

popularity %>% 
  ggplot(aes(x = Week, y = Popularity, group = `Search Term`, color = `Search Term`)) +
  geom_line() +
  labs(x = "Time", y = "Popularity", title = "Week: {frame_along}") +
  theme_classic() +
  theme(legend.position = "top") +
  transition_reveal(Week) +
  ???
popularity %>% 
  ggplot(aes(x = Week, y = Popularity, group = `Search Term`, color = `Search Term`)) +
  geom_line() +
  labs(x = "Time", y = "Popularity", title = "Week: {frame_along}") +
  theme_classic() +
  theme(legend.position = "top") +
  transition_reveal(Week) +
  view_follow(fixed_y = TRUE)

This is a different way to show data populating over time and can be useful when the data is pretty spread out.

Rendering

We want to be able to save and display our animations in a good format for viewing. Saving animations can save space and time to load them, and adjusting features like duration can also greatly improve the final animation. There are a few functions that help with that.

To Do:

To save animations as gifs you can use the anim_save() function, imputing the desired file name in the parentheses. Then, to load it use the knitr::include_graphics() function, with the file name in the parentheses. Try it out on a previous plot.

popularity %>% 
  ggplot(aes(x = Week, y = Popularity, group = `Search Term`, color = `Search Term`)) +
  geom_line() +
  labs(x = "Time", y = "Popularity", title = "Week: {frame_along}") +
  theme_classic() +
  theme(legend.position = "top") +
  transition_reveal(Week)

anim_save("popularity_anim")
knitr::include_graphics("popularity_anim")

To adjust how animations are displayed, including duration, height/width, fps, and more, the animate() function is useful. Key arguments that can be changed are:

  • fps
  • duration
  • width
  • height
  • start_pause
  • end_pause
  • rewind

To Do:

Save your plot and then try adjusting some of these (like the ‘fps’ setting)to see how the output of the animation changes.

p <- popularity %>% 
  ggplot(aes(x = Week, y = Popularity, group = `Search Term`, color = `Search Term`)) +
  geom_line() +
  labs(x = "Time", y = "Popularity", title = "Week: {frame_along}") +
  theme_classic() +
  theme(legend.position = "top") +
  transition_reveal(Week)
animate(p, width = 700, height = 425, fps = 25, duration = 16, rewind = FALSE, start_pause = 25, end_pause = 25)

These adjustments can greatly improve how the animation is displayed, but note that it can slow down processing time.

Transition_filter()

Now, perhaps we wanted to iterate through the different search terms, rather than seeing them all at once. In this case, we can use transition_filter(). This transition allows you to animate trough a range of filtering conditions – in this case Search Term – and see the data separately for each. This can be a cleaner way to view multiple categories of data, but makes comparison between them harder.

To Do:

Using the same plot as before, change transition_reveal() to transition_filter(). Then, add in the following arguments: transition_length, filter_length, and the filter conditions. These conditions should be the Search Term equal to each of your terms. Also, update the title label to the corresponding variable: “{closest_filter}”. See below for reference:

Note: you might need to install the ‘transformr’ package.

popularity %>% 
  ggplot(aes(x = Week, y = Popularity, group = `Search Term`, color = `Search Term`)) +
  geom_line() +
  labs(x = "Time", y = "Popularity", title = "{closest_filter}") +
  theme_classic() +
  theme(legend.position = "top") +
  transition_filter(transition_length = 0.05,
                    filter_length = 2, `Search Term` == "var1", `Search Term` == "var2", `Search Term` == "var3")
popularity %>% 
  ggplot(aes(x = Week, y = Popularity, group = `Search Term`, color = `Search Term`)) +
  geom_line() +
  labs(x = "Time", y = "Popularity", title = "{closest_filter}") +
  theme_classic() +
  theme(legend.position = "top") +
  transition_filter(transition_length = 0.05,
                    filter_length = 2,
                    `Search Term` == "Premier League", `Search Term` == "UEFA Champions League", `Search Term` == "World cup")

When we run the plot we now get the animation transitioning through each of these filters, providing a separate view of each of the search terms’ popularity over time.

Transition_states()

Another transition function is transition_states(). This function allows you to animate between several distinct stages of the data, whether that be before/during/after an event, by time segment, or another relevant partition.

To Do:

Before we use this, lets focus our data down to a smaller section of time for one category. Filter your data to include only one term for the most recent 4-6 weeks of data, like so:

recent_term <- popularity %>% 
  filter(Week > "2022-08-01",
         `Search Term` == "var1")
recent_term <- popularity %>% 
  filter(Week > "2022-08-01",
         `Search Term` == "Premier League")

Now, create a bar chart of this data, with Week on the x-axis and Popularity on the y-axis. Coloring by Popularity can also make this plot more visually appealing, but is not necessary.

recent_term %>% 
  ggplot(aes(x = Week, y = Popularity, fill = Popularity)) +
  geom_col() +
  scale_fill_distiller(palette = "Greens", direction = 1) +
  theme_classic()

From here, we can add in the transition_states() function, as we have distinct stages (weeks) of data. Use the Week variable as the first argument in the transition function, and don’t forget to add a label for the transition! In this case you have a few options for the label, if you want to display the previous, current, on next state respectively.

recent_term %>% 
  ggplot(aes(x = Week, y = Popularity, fill = Popularity)) +
  geom_col() +
  scale_fill_distiller(palette = "Greens", direction = 1) +
  theme_classic() +
  labs(title = "Week: {closest_state}") +
  transition_states(Week, wrap = FALSE)

Shadows

The previous animation you made was pretty neat, but it only really provided useful comparison for successive weeks, and not across the whole time span. Wouldn’t it be great if there was a way to keep the previous values displayed during the animation? Well, there is – with shadows!

Shadows allow you to show previous data during the course of an animation. The three shadow functions are:

  • shadow_mark()
  • shadow_trail()
  • shadow_wake()

Each offer different ways of representing past data in an animation, as their names suggest.

To Do:

Try adding shadow_mark() to the previous bar chart animation and see what it does.

recent_term %>% 
  ggplot(aes(x = Week, y = Popularity, fill = Popularity)) +
  geom_col() +
  scale_fill_distiller(palette = "Greens", direction = 1) +
  theme_classic() +
  labs(title = "Week: {closest_state}") +
  transition_states(Week, wrap = FALSE) +
  ???
recent_term %>% 
  ggplot(aes(x = Week, y = Popularity, fill = Popularity)) +
  geom_col() +
  scale_fill_distiller(palette = "Greens", direction = 1) +
  theme_classic() +
  labs(title = "Week: {closest_state}") +
  transition_states(Week, wrap = FALSE) +
  shadow_mark()

Now we can see the previous bars throughout the animation, which makes comparison across weeks a bit easier.

We will explore the other shadow functions later in other plots.

New Data

Some transition functions work better on different types of data. To showcase the other functions, we will switch to the gapminder data set. This is a great example data set for animations, and it contains data on worldwide economic, health, and other public information.

To Do:

Load the data set and preview it below:

head(gapminder)
## # A tibble: 6 × 6
##   country     continent  year lifeExp      pop gdpPercap
##   <fct>       <fct>     <int>   <dbl>    <int>     <dbl>
## 1 Afghanistan Asia       1952    28.8  8425333      779.
## 2 Afghanistan Asia       1957    30.3  9240934      821.
## 3 Afghanistan Asia       1962    32.0 10267083      853.
## 4 Afghanistan Asia       1967    34.0 11537966      836.
## 5 Afghanistan Asia       1972    36.1 13079460      740.
## 6 Afghanistan Asia       1977    38.4 14880372      786.

We will start again by creating a plot using some of the 6 variables in the data set. Here’s an example:

gapminder %>% 
  ggplot(aes(x = gdpPercap, y = lifeExp, color = country, size = pop)) +
  geom_point(alpha = 0.75, show.legend = FALSE) +
  scale_color_viridis_d() +
  labs(x = "GDP per capita", y = "Life expectancy") +
  theme_classic()

Lets add a log transformation so we can see the data a bit better:

gapminder %>% 
  ggplot(aes(x = gdpPercap, y = lifeExp, color = country, size = pop)) +
  geom_point(alpha = 0.75, show.legend = FALSE) +
  scale_color_viridis_d() +
  scale_x_log10() +
  labs(x = "GDP per capita", y = "Life expectancy") +
  theme_classic()

Transiton_layers()

In some cases, you may want to show multiple geoms on the same plot, like a line and bar chart together. Transition_layers() allows for this functionality, and adds these layers in one-by-one during the animation.

To Do:

First, lets edit the previous plot and add some more geom elements to it. Remove the coloring and size, and add a histogram of the GDP Per Capita and a geom_smooth of the points.

gapminder %>% 
  ggplot() +
  geom_histogram(aes(x = gdpPercap, alpha = 0.5, show.legend = FALSE)) +
  geom_point(aes(x = gdpPercap, y = lifeExp, show.legend = FALSE)) +
  geom_smooth(aes(x = gdpPercap, y = lifeExp, stat = "smooth")) +
  scale_color_viridis_d() +
  scale_x_log10() +
  labs(x = "GDP per capita", y = "Life expectancy / Count", alpha = "GDP") +
  theme_classic()

Now, lets add transition_layer() to animate this plot.

gapminder %>% 
  ggplot() +
  geom_histogram(aes(x = gdpPercap, alpha = 0.5, show.legend = FALSE)) +
  geom_point(aes(x = gdpPercap, y = lifeExp, show.legend = FALSE)) +
  geom_smooth(aes(x = gdpPercap, y = lifeExp, stat = "smooth")) +
  scale_color_viridis_d() +
  scale_x_log10() +
  labs(x = "GDP per capita", y = "Life expectancy / Count", alpha = "GDP") +
  theme_classic() +
  ???
gapminder %>% 
  ggplot() +
  geom_histogram(aes(x = gdpPercap, alpha = 0.5, show.legend = FALSE)) +
  geom_point(aes(x = gdpPercap, y = lifeExp, show.legend = FALSE)) +
  geom_smooth(aes(x = gdpPercap, y = lifeExp, stat = "smooth")) +
  scale_color_viridis_d() +
  scale_x_log10() +
  labs(x = "GDP per capita", y = "Life expectancy / Count", alpha = "GDP") +
  theme_classic() +
  transition_layers()

This can be a great way to show multiple types of plots layered together as seen here.

Transition_time()

As the name implies, this function allows you to transition through distinct states in time, like year. This is a better option for point data, whereas transition_reveal() is better for line data.

To Do:

To see it in action, add transition_time() to the original gapminder plot, with year as the argument, and adding the correct label for this function.

gapminder %>% 
  ggplot(aes(x = gdpPercap, y = lifeExp, color = country, size = pop)) +
  geom_point(alpha = 0.75, show.legend = FALSE) +
  scale_color_viridis_d() +
  scale_x_log10() +
  labs(x = "GDP per capita", y = "Life expectancy", title = "Year: ???") +
  theme_classic() +
  ???
gapminder %>% 
  ggplot(aes(x = gdpPercap, y = lifeExp, color = country, size = pop)) +
  geom_point(alpha = 0.75, show.legend = FALSE) +
  scale_color_viridis_d() +
  scale_x_log10() +
  labs(x = "GDP per capita", y = "Life expectancy", title = "Year: {frame_time}") +
  theme_classic() +
  transition_time(year)

Try adding another shadow function to this animation:

gapminder %>% 
  ggplot(aes(x = gdpPercap, y = lifeExp, color = country, size = pop)) +
  geom_point(alpha = 0.75, show.legend = FALSE) +
  scale_color_viridis_d() +
  scale_x_log10() +
  labs(x = "GDP per capita", y = "Life expectancy", title = "Year: {frame_time}") +
  theme_classic() +
  transition_time(year) +
  shadow_wake(wake_length = 0.2)

This really highlights the general trend of increasing life expectancy & GDP per-capita over time.

Easing

Suppose you wanted to change the rate of the transition in the animation. You could go about doing that via easing. Easing controls the “acceleration” per-se of the animation. By default it is linear/constant speed during transitions as we have seen, but you could make it slow down or speed up during the transitions too. This is done via the ease_aes() function.

To have slow-to-fast transitions we can use the argument: quadratic-in and to have fast-to-slow transitions down use the argument: quadratic-out.

To Do:

Try adding one of these to the previous animation.

gapminder %>% 
  ggplot(aes(x = gdpPercap, y = lifeExp, color = country, size = pop)) +
  geom_point(alpha = 0.75, show.legend = FALSE) +
  scale_color_viridis_d() +
  scale_x_log10() +
  labs(x = "GDP per capita", y = "Life expectancy", title = "Year: {frame_time}") +
  theme_classic() +
  transition_time(year) +
  shadow_wake(wake_length = 0.2) +
  ease_aes('quadratic-out')

Conslusion

Creating animations in R is relatively easy and quite fun to do. They are also very practical in that they can showcase different elements and dimensions of data that a static plot simply cannot. From this tutorial, you now have the skills necessary to create, edit, and save animations like the one below.

To Do:

One final task: Edit your last visualization to include each of the 5 animation components we covered:

  • Labels
  • Views
  • Rendering
  • Shadows
  • Easing
final_plot <- gapminder %>% 
  ggplot(aes(x = gdpPercap, y = lifeExp, color = country, size = pop)) +
  geom_point(alpha = 0.75, show.legend = FALSE) +
  facet_wrap(~continent) +
  scale_color_viridis_d() +
  scale_x_log10() +
  labs(x = "GDP per capita", y = "Life expectancy", title = "Year: {frame_time}") +
  theme_classic() +
  transition_time(year) +
  shadow_wake(wake_length = 0.2) +
  ease_aes('linear') +
  view_follow()

animate(final_plot, fps = 20, duration = 10, rewind = FALSE, start_pause = 20, end_pause = 20)


Notes:

Feedback:

  • Give examples of when to use certain animation functions & what type of data they are best for.
  • Add more comments to identify what info is general to gganimate and what is specific to the individual data we are working with.
  • Think about potentially reducing the amount of text or separating it a little better.

Improvements made:

  • Added in more info to the beginning and end of transition functions sections that specifically talked about use cases, when the animation is useful, and data that works well with that type of animation.
  • Added in bolded text indicating where the user is meant to write code. This should help separate the general & the specific information more and make it clearer to the user what they are supposed to do in the guide. Also generalized some terms in the code blocks and added more specific code in hidden code chunks for reference.
  • Considered removing some text, but ultimately think what’s included is all pretty necessary information. Also, adding in the “to-do” identifiers should help break up the text a bit more and make it more manageable in smaller pieces.